Skip to content

feat(condo): DOMA-12905 add mutation to send voip start message to all verified residents on address + unit#7204

Open
YEgorLu wants to merge 6 commits intomainfrom
feat/condo/DOMA-12905/send-voip-start-push-message
Open

feat(condo): DOMA-12905 add mutation to send voip start message to all verified residents on address + unit#7204
YEgorLu wants to merge 6 commits intomainfrom
feat/condo/DOMA-12905/send-voip-start-push-message

Conversation

@YEgorLu
Copy link
Copy Markdown
Contributor

@YEgorLu YEgorLu commented Feb 16, 2026

Summary by CodeRabbit

Release Notes

New Features

  • Added a sendVoIPStartMessage GraphQL mutation to initiate VoIP calls with support for both B2C app and native call handling
  • Provides message delivery statistics including verified contact count, created message count, and error count
  • Includes VoIP call status tracking functionality
  • Added new GraphQL input/output types and unit type enums for VoIP messaging
  • Added multi-language error messages for VoIP API operations

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduces a new sendVoIPStartMessage GraphQL mutation that validates input call data, resolves target properties and contacts/residents, sends VoIP incoming-call notifications to eligible recipients with native or B2C-app-driven payload, and persists call status to Redis with a 3-minute TTL. Includes access controls, GraphQL schema definitions, test utilities, and multi-language error messaging.

Changes

Cohort / File(s) Summary
Core VoIP Service Implementation
apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js, apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js
Main service with resolver logic: validates input/DV/sender, resolves property/app/contacts/residents, builds recipient list, constructs message payloads with native or B2C-app call data (including custom-field-driven voipType priority), sends messages concurrently, caches call status to Redis on success. Comprehensive test suite validating access control, input validation, message statistics, cache behavior, and voipType/payload mapping.
GraphQL Schema & Types
apps/condo/schema.graphql, apps/condo/schema.ts, apps/condo/domains/miniapp/gql.js, apps/condo/domains/miniapp/schema/index.js
Adds new GraphQL input types (SendVoIPStartMessageInput, SendVoIPStartMessageData, native/B2C call data variants, VoIPPanel), output type (SendVoIPStartMessageOutput with counters), enum (AllowedVoIPMessageUnitType), mutation field on root Mutation type, and TypeScript type definitions. Exports GraphQL mutation constant SEND_VOIP_START_MESSAGE_MUTATION.
Access Control & Authorization
apps/condo/domains/miniapp/access/SendVoIPStartMessageService.js, apps/condo/domains/miniapp/utils/b2cAppServiceUserAccess/config.js, apps/condo/domains/miniapp/utils/b2cAppServiceUserAccess/server.utils.js, apps/condo/domains/miniapp/schema/B2CAppAccessRightSet.test.js
Adds async authorization helper enforcing authentication, soft-delete check, admin pass-through, and service-user delegation via canExecuteServiceAsB2CAppServiceUser. Enables sendVoIPStartMessage in service-user access schema. Updates permission-gating logic to use explicit false check instead of truthy evaluation. Extends B2CAppAccessRightSet tests to verify service-user permission scenarios.
VoIP Utilities & Test Helpers
apps/condo/domains/miniapp/utils/voip.js, apps/condo/domains/miniapp/utils/testSchema/index.js
Introduces VoIP call-status KV store utilities (setCallStatus/getCallStatus with 3-min TTL, callId validation/normalization) and Redis-backed caching with base64-encoded keys. Adds test helper sendVoIPStartMessageByTestClient with sender construction and error handling.
Constants & Localization
apps/condo/domains/miniapp/constants.js, apps/condo/lang/en/en.json, apps/condo/lang/es/es.json, apps/condo/lang/ru/ru.json
Adds constants for CALL_DATA_NOT_PROVIDED_ERROR, NATIVE_VOIP_TYPE ('sip'), B2C_APP_VOIP_TYPE ('b2cApp'). Provides error messages in English, Spanish, and Russian for PROPERTY_NOT_FOUND, APP_NOT_FOUND, DV_VERSION_MISMATCH, WRONG_SENDER_FORMAT.
Notification Schema
apps/condo/domains/notification/constants/constants.js
Extends VOIP_INCOMING_CALL_MESSAGE_TYPE meta schema with optional voipPanels and stunServers fields.
Icon Enum Extension
apps/condo/schema.graphql
Expands B2BAppIconType enum with ten new values: Bold, Italic, Mic, MicOff, NumberList, Redo, RemoveFormatting, Undo, Volume, VolumeOff.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Service as SendVoIPStartMessageService
    participant PropertyDB as Property/Contact/Resident DB
    participant MessageSender as Message Sender
    participant Redis as Redis Cache

    Client->>Service: sendVoIPStartMessage(input)
    Service->>Service: Validate authentication & authorization
    Service->>Service: Check DV & sender format
    Service->>PropertyDB: Resolve B2CAppProperty by app + addressKey
    Service->>PropertyDB: Fetch verified Contacts for unit
    Service->>PropertyDB: Load Residents for same address/unit
    Service->>PropertyDB: Load custom values (voip* fields)
    
    loop For each eligible recipient
        Service->>Service: Build message payload<br/>(native or B2C app call data)
        Service->>MessageSender: sendMessage(VOIP_INCOMING_CALL_MESSAGE_TYPE)
    end
    
    MessageSender-->>Service: Message send results
    Service->>Service: Classify successes/failures
    
    alt Any messages succeeded
        Service->>Redis: setCallStatus(CALL_STATUS_START_SENT)
    end
    
    Service-->>Client: {verifiedContactsCount, createdMessagesCount, erroredMessagesCount}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #7189: Adds/modifies B2C service-user access controls and permission checks; directly connected through shared service-user access utilities and config file changes.

Suggested reviewers

  • SavelevMatthew
  • abshnko

Poem

🐰 Hop, hop, to send a VoIP call start,
We validate, resolve, and chart the cart!
Redis caches every ringing song,
While GraphQL mutations make things belong.
Tests and access controls keep it tight,
A whimsical feature, burning bright! 📞✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main feature: adding a new mutation to send VoIP start messages to verified residents on a specific address and unit, which aligns with the substantial changes implementing this functionality.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/condo/DOMA-12905/send-voip-start-push-message

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6ca3b8681

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +359 to +362
const requiredMetaData = get(MESSAGE_META[VOIP_INCOMING_CALL_MESSAGE_TYPE], 'data', {})
const metaData = Object.fromEntries(
Object.keys(requiredMetaData).map((key) => [key, preparedDataArgs[key]])
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include voipPanels in generated VOIP message metadata

This resolver builds meta.data strictly from keys present in MESSAGE_META[VOIP_INCOMING_CALL_MESSAGE_TYPE].data, so any input field not listed there is silently dropped. Even though SendVoIPStartMessageData accepts voipPanels and preparedDataArgs includes it, this mapping strips it before sendMessage, which means clients cannot receive multi-panel commands from this new mutation despite the API contract advertising that field.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js`:
- Around line 49-57: The ERRORS entry PROPERTY_NOT_FOUND uses the wrong key
`query: SERVICE_NAME`; change `query` to `mutation` so it matches the mutation
error shape used by other entries (e.g., APP_NOT_FOUND) — update the
PROPERTY_NOT_FOUND object in the ERRORS map to use `mutation: SERVICE_NAME`
instead of `query: SERVICE_NAME` to keep the schema consistent for the
SendVoIPStartMessageService.
- Around line 387-415: The map over sendMessageResults already updates
logInfoStats (createdMessagesCount and erroredMessagesCount) and produces
sendMessageStats, so remove the subsequent for (const messageStat of
sendMessageStats) loop entirely to avoid double-counting; ensure any later code
still reads sendMessageStats (or its filtered results) and that
MESSAGE_SENDING_STATUS logic remains in the existing map block (no other changes
to logInfoStats are needed).

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js`:
- Line 243: The test's uniqueness assertion uses new Set on msg.user (an
object), which compares by reference and won't deduplicate by value; update the
assertion to deduplicate by the user identifier instead by mapping
createdMessages to the user id (e.g., use createdMessages.map(msg =>
msg.user.id) or msg.userId) before creating the Set and asserting its length so
duplicates are correctly collapsed; ensure you reference createdMessages and
msg.user (or msg.userId) in the change.

In `@apps/condo/lang/en/en.json`:
- Around line 464-469: The translation key
"api.miniapp.sendVoIPStartMessage.WRONG_SENDER_FORMAT" in
apps/condo/lang/en/en.json is missing the {details} placeholder; update its
value to include the "{details}" placeholder (matching the pattern used by
sendB2CAppPushMessage) so that the backend messageInterpolation from
dvAndSender.js can be rendered correctly.

In `@apps/condo/lang/es/es.json`:
- Line 464: Update the Spanish message for the locale key
"api.miniapp.sendVoIPStartMessage.PROPERTY_NOT_FOUND" to use the consistent term
"comunidad" instead of "edificio"; modify the JSON value so it reads something
like "No se encontró la comunidad para el \"addressKey\" proporcionado" while
preserving the key name and proper escaping of the quotes.

In `@apps/condo/schema.graphql`:
- Around line 92024-92108: The GraphQL description contains a typo ("Dtfm
command for panel") originating from the source constant in
SendVoIPStartMessageService.js; open that file, locate the input/field
description string 'Dtfm command for panel' (used to generate the
VoIPPanel.dtfmCommand description) and change it to 'DTMF command for panel',
then re-run whatever schema generation/build step produces
apps/condo/schema.graphql so the corrected description is propagated to the
autogenerated schema.

In `@apps/condo/schema.ts`:
- Around line 115379-115384: The generated GraphQL type VoIpPanel contains a
misspelled field dtfmCommand that must be renamed to dtmfCommand; because
schema.ts is autogenerated, update the source schema/SDL or model that defines
VoIpPanel to use dtmfCommand, then update all downstream references to the old
name (e.g. SendVoIPStartMessageService.js — the call/site around the dtfmCommand
usage, SendB2CAppPushMessageService.js, notification constants, and related
tests) to use dtmfCommand so the GraphQL API and runtime code stay consistent;
run codegen/build and update any serializers/deserializers or tests that
reference dtfmCommand to the corrected symbol.
🧹 Nitpick comments (8)
apps/condo/domains/miniapp/access/SendVoIPStartMessageService.js (2)

5-5: Unused data destructuring.

data is destructured from args but never referenced in the function body. Remove it to avoid confusion.

Proposed fix
-async function canSendVoIPStartMessage ({ args: { data }, authentication: { item: user } }) {
+async function canSendVoIPStartMessage ({ authentication: { item: user } }) {

10-12: Empty if block for SERVICE users silently falls through to return false.

The TODO is noted, but the empty block is easy to miss during future maintenance. Consider adding an explicit return false inside or converting to a comment-only guard so intent is clearer.

Would you like me to open an issue to track the SERVICE-type user access implementation (the TODO on line 11)?

apps/condo/domains/miniapp/utils/voip.js (2)

20-24: Biome control-character warning is a false positive here — consider adding a suppression comment.

The regex intentionally strips control characters (\u0000-\u001F) from callId for sanitization. The existing eslint-disable comment covers ESLint, but Biome still flags it. If the project runs Biome in CI, you may want to add a Biome-specific suppression or a brief rationale comment so future readers understand the intent.


32-40: Inconsistent return type: false on validation failure vs. Redis response string on success.

setCallStatus returns false when callId is invalid (line 33) but returns the raw result of kv.set(...) on success (typically the string 'OK'). Callers checking === false won't break, but a consistent boolean return would be clearer.

Proposed fix
 async function setCallStatus ({ b2cAppId, callId, status, startingMessagesIdsByUserIds }) {
     if (!validateCallId(callId)) return false
-    return kv.set(
+    await kv.set(
         buildKey(b2cAppId, normalizeCallId(callId)),
         JSON.stringify({ status, startingMessagesIdsByUserIds }),
         'EX',
         CALL_STATUS_TTL_IN_SECONDS,
     )
+    return true
 }
apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js (1)

85-97: Test name says "no app with provided id" but the expected error is PROPERTY_NOT_FOUND.

The test description mentions a missing app, yet it asserts ERRORS.PROPERTY_NOT_FOUND. If the service resolves the app via B2CAppProperty lookup first, this is technically correct behavior, but the test name is misleading. Consider renaming to clarify the actual validation path (e.g., "should throw error if no B2CAppProperty found for app and addressKey").

apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js (3)

297-297: forEach callback should not return a value (flagged by static analysis).

The arrow function user => userIdToLocale[user.id] = user.locale implicitly returns the assignment result. Use a block body to avoid this.

Proposed fix
-                usersOfContacts.forEach(user => userIdToLocale[user.id] = user.locale)
+                usersOfContacts.forEach(user => { userIdToLocale[user.id] = user.locale })

76-78: Logging: entityName should be entity, and err is misleading when there are no errors.

Per the coding guidelines, the standard structured logging field is entity, not entityName. Additionally, err: errors is passed even on success paths where errors is undefined—consider only including it when errors are present, to avoid noisy log entries.

Proposed fix
-const logInfo = ({ b2cAppId, callId, stats, errors }) => {
-    logger.info({ msg: `${SERVICE_NAME} stats`, entityName: 'B2CApp', entityId: b2cAppId, data: { callId, stats }, err: errors })
+const logInfo = ({ b2cAppId, callId, stats, errors }) => {
+    logger.info({ msg: `${SERVICE_NAME} stats`, entity: 'B2CApp', entityId: b2cAppId, data: { callId, stats }, ...(errors ? { err: errors } : {}) })
 }

As per coding guidelines, "Use structured logging with standard fields (msg, data, entityId, entity, count, status, err)".


339-341: Remove commented-out code.

Line 340 contains a commented-out .filter(...) call referencing rateLimitsErrorsByUserIds which doesn't exist in this file. This is leftover debug/development code that adds noise.

Proposed fix
                 const sendMessagePromises = verifiedResidentsWithUniqueUsers
-                    // .filter(resident => !rateLimitsErrorsByUserIds[resident.user])
                     .map(async (resident) => {

Comment on lines +49 to +57
const ERRORS = {
PROPERTY_NOT_FOUND: {
query: SERVICE_NAME,
variable: ['data', 'addressKey'],
code: BAD_USER_INPUT,
type: PROPERTY_NOT_FOUND_ERROR,
message: 'Unable to find Property or B2CAppProperty by provided addressKey',
messageForUser: `api.miniapp.${SERVICE_NAME}.PROPERTY_NOT_FOUND`,
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent field: query should be mutation.

PROPERTY_NOT_FOUND on line 51 uses query: SERVICE_NAME, while APP_NOT_FOUND on line 59 and the other errors use mutation: SERVICE_NAME. Since this is a mutation, the field should be mutation for consistency and correctness.

Proposed fix
     PROPERTY_NOT_FOUND: {
-        query: SERVICE_NAME,
+        mutation: SERVICE_NAME,
         variable: ['data', 'addressKey'],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ERRORS = {
PROPERTY_NOT_FOUND: {
query: SERVICE_NAME,
variable: ['data', 'addressKey'],
code: BAD_USER_INPUT,
type: PROPERTY_NOT_FOUND_ERROR,
message: 'Unable to find Property or B2CAppProperty by provided addressKey',
messageForUser: `api.miniapp.${SERVICE_NAME}.PROPERTY_NOT_FOUND`,
},
const ERRORS = {
PROPERTY_NOT_FOUND: {
mutation: SERVICE_NAME,
variable: ['data', 'addressKey'],
code: BAD_USER_INPUT,
type: PROPERTY_NOT_FOUND_ERROR,
message: 'Unable to find Property or B2CAppProperty by provided addressKey',
messageForUser: `api.miniapp.${SERVICE_NAME}.PROPERTY_NOT_FOUND`,
},
🤖 Prompt for AI Agents
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 49 - 57, The ERRORS entry PROPERTY_NOT_FOUND uses the wrong key `query:
SERVICE_NAME`; change `query` to `mutation` so it matches the mutation error
shape used by other entries (e.g., APP_NOT_FOUND) — update the
PROPERTY_NOT_FOUND object in the ERRORS map to use `mutation: SERVICE_NAME`
instead of `query: SERVICE_NAME` to keep the schema consistent for the
SendVoIPStartMessageService.

Comment on lines +387 to +415
const sendMessageStats = sendMessageResults.map(promiseResult => {
if (promiseResult.status === 'rejected') {
logInfoStats.erroredMessagesCount++
logInfoStats.createMessageErrors.push(promiseResult.reason)
return { error: promiseResult.reason }
}
const { resident, result } = promiseResult.value
if (result.isDuplicateMessage) {
logInfoStats.erroredMessagesCount++
logInfoStats.createMessageErrors.push(`${resident.id} duplicate message`)
return { error: `${resident.id} duplicate message` }
}
if (result.status !== MESSAGE_SENDING_STATUS) {
logInfoStats.erroredMessagesCount++
logInfoStats.createMessageErrors.push(`${resident.id} invalid status for some reason`)
return { error: `${resident.id} invalid status for some reason` }
}
logInfoStats.createdMessagesCount++
return result
})

for (const messageStat of sendMessageStats) {
if (messageStat.error) {
logInfoStats.erroredMessagesCount++
logInfoStats.createMessageErrors.push(messageStat.error)
continue
}
logInfoStats.createdMessagesCount++
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bug: logInfoStats counters are incremented twice — once in the .map() (lines 387–406) and again in the for loop (lines 408–415).

The .map() on lines 387–406 already increments logInfoStats.erroredMessagesCount and logInfoStats.createdMessagesCount. The for loop on lines 408–415 iterates over the same sendMessageStats array and increments them again. This causes logged stats to report 2× the real counts.

The returned values (lines 432–436) are computed independently from sendMessageStats.filter(...) and are correct, so this only corrupts the logged metrics—but that makes debugging and monitoring unreliable.

Pick one loop or the other. The simplest fix is to remove the second for loop entirely, since the .map() already accumulates the stats:

Proposed fix — remove the redundant second loop
-                const sendMessageStats = sendMessageResults.map(promiseResult => {
+                const sendMessageStats = sendMessageResults.map((promiseResult) => {
                     if (promiseResult.status === 'rejected') {
                         logInfoStats.erroredMessagesCount++
                         logInfoStats.createMessageErrors.push(promiseResult.reason)
                         return { error: promiseResult.reason }
                     } 
                     const { resident, result } = promiseResult.value
                     if (result.isDuplicateMessage) {
                         logInfoStats.erroredMessagesCount++
                         logInfoStats.createMessageErrors.push(`${resident.id} duplicate message`)
                         return { error: `${resident.id} duplicate message` }
                     }
                     if (result.status !== MESSAGE_SENDING_STATUS) {
                         logInfoStats.erroredMessagesCount++
                         logInfoStats.createMessageErrors.push(`${resident.id} invalid status for some reason`)
                         return { error: `${resident.id} invalid status for some reason` }
                     }
                     logInfoStats.createdMessagesCount++
                     return result
                 })
-                
-                for (const messageStat of sendMessageStats) {
-                    if (messageStat.error) {
-                        logInfoStats.erroredMessagesCount++
-                        logInfoStats.createMessageErrors.push(messageStat.error)
-                        continue
-                    }
-                    logInfoStats.createdMessagesCount++
-                }
🤖 Prompt for AI Agents
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 387 - 415, The map over sendMessageResults already updates logInfoStats
(createdMessagesCount and erroredMessagesCount) and produces sendMessageStats,
so remove the subsequent for (const messageStat of sendMessageStats) loop
entirely to avoid double-counting; ensure any later code still reads
sendMessageStats (or its filtered results) and that MESSAGE_SENDING_STATUS logic
remains in the existing map block (no other changes to logInfoStats are needed).

}, { first: residentsCount })

expect(createdMessages).toHaveLength(residentsCount)
expect([...new Set(createdMessages.map(msg => msg.user))]).toHaveLength(residentsCount)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Set on user objects does not deduplicate by value — assertion is weaker than intended.

msg.user is likely an object (e.g., { id: '...' }), so new Set(createdMessages.map(msg => msg.user)) will never collapse duplicates (objects are reference-compared). Map to the ID first:

Proposed fix
-            expect([...new Set(createdMessages.map(msg => msg.user))]).toHaveLength(residentsCount)
+            expect([...new Set(createdMessages.map(msg => msg.user.id))]).toHaveLength(residentsCount)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect([...new Set(createdMessages.map(msg => msg.user))]).toHaveLength(residentsCount)
expect([...new Set(createdMessages.map(msg => msg.user.id))]).toHaveLength(residentsCount)
🤖 Prompt for AI Agents
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js` at
line 243, The test's uniqueness assertion uses new Set on msg.user (an object),
which compares by reference and won't deduplicate by value; update the assertion
to deduplicate by the user identifier instead by mapping createdMessages to the
user id (e.g., use createdMessages.map(msg => msg.user.id) or msg.userId) before
creating the Set and asserting its length so duplicates are correctly collapsed;
ensure you reference createdMessages and msg.user (or msg.userId) in the change.

Comment thread apps/condo/lang/en/en.json Outdated
Comment on lines +464 to +469
"api.miniapp.sendVoIPStartMessage.PROPERTY_NOT_FOUND": "Building not found for the provided \"addressKey\"",
"api.miniapp.sendVoIPStartMessage.APP_NOT_FOUND": "Application not found for the provided \"id\"",
"api.miniapp.sendVoIPStartMessage.DV_VERSION_MISMATCH": "Invalid data version value",
"api.miniapp.sendVoIPStartMessage.WRONG_SENDER_FORMAT": "Invalid format for the \"sender\" field",
"api.miniapp.sendVoIPStartMessage.pushData.title": "Intercom call",
"api.miniapp.sendVoIPStartMessage.pushData.body": "Intercom call",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "WRONG_SENDER_FORMAT" -g '!**/node_modules/**' --max-count=50

Repository: open-condo-software/condo

Length of output: 12383


🏁 Script executed:

rg -n "sendVoIPStartMessage" -g '!**/node_modules/**' -A 5 -B 5 | head -100

Repository: open-condo-software/condo

Length of output: 10455


🏁 Script executed:

rg -n "sendB2CAppPushMessage" -g '!**/node_modules/**' | grep -i "wrong_sender_format"

Repository: open-condo-software/condo

Length of output: 470


🏁 Script executed:

rg -n "api.miniapp.sendB2CAppPushMessage.WRONG_SENDER_FORMAT|api.miniapp.sendVoIPStartMessage.WRONG_SENDER_FORMAT" apps/condo/lang/ --multiline -B 0 -A 0

Repository: open-condo-software/condo

Length of output: 861


🏁 Script executed:

git diff HEAD -- apps/condo/lang/ | grep -A 2 -B 2 "WRONG_SENDER_FORMAT" | head -60

Repository: open-condo-software/condo

Length of output: 51


Add {details} placeholder to sendVoIPStartMessage.WRONG_SENDER_FORMAT translation.

The backend passes a details field via messageInterpolation for this error (as seen in dvAndSender.js). The English translation for sendB2CAppPushMessage correctly includes the {details} placeholder; sendVoIPStartMessage should match this pattern.

Fix
-  "api.miniapp.sendVoIPStartMessage.WRONG_SENDER_FORMAT": "Invalid format for the \"sender\" field",
+  "api.miniapp.sendVoIPStartMessage.WRONG_SENDER_FORMAT": "Invalid format for the \"sender\" field. {details}",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"api.miniapp.sendVoIPStartMessage.PROPERTY_NOT_FOUND": "Building not found for the provided \"addressKey\"",
"api.miniapp.sendVoIPStartMessage.APP_NOT_FOUND": "Application not found for the provided \"id\"",
"api.miniapp.sendVoIPStartMessage.DV_VERSION_MISMATCH": "Invalid data version value",
"api.miniapp.sendVoIPStartMessage.WRONG_SENDER_FORMAT": "Invalid format for the \"sender\" field",
"api.miniapp.sendVoIPStartMessage.pushData.title": "Intercom call",
"api.miniapp.sendVoIPStartMessage.pushData.body": "Intercom call",
"api.miniapp.sendVoIPStartMessage.PROPERTY_NOT_FOUND": "Building not found for the provided \"addressKey\"",
"api.miniapp.sendVoIPStartMessage.APP_NOT_FOUND": "Application not found for the provided \"id\"",
"api.miniapp.sendVoIPStartMessage.DV_VERSION_MISMATCH": "Invalid data version value",
"api.miniapp.sendVoIPStartMessage.WRONG_SENDER_FORMAT": "Invalid format for the \"sender\" field. {details}",
"api.miniapp.sendVoIPStartMessage.pushData.title": "Intercom call",
"api.miniapp.sendVoIPStartMessage.pushData.body": "Intercom call",
🤖 Prompt for AI Agents
In `@apps/condo/lang/en/en.json` around lines 464 - 469, The translation key
"api.miniapp.sendVoIPStartMessage.WRONG_SENDER_FORMAT" in
apps/condo/lang/en/en.json is missing the {details} placeholder; update its
value to include the "{details}" placeholder (matching the pattern used by
sendB2CAppPushMessage) so that the backend messageInterpolation from
dvAndSender.js can be rendered correctly.

"api.miniapp.sendB2CAppPushMessage.RESIDENT_NOT_FOUND": "No se ha encontrado el vecino con el id introducido",
"api.miniapp.sendB2CAppPushMessage.USER_NOT_FOUND": "No se ha encontrado el usuario con el id introducido",
"api.miniapp.sendB2CAppPushMessage.WRONG_SENDER_FORMAT": "Formato del campo \"sender\" no válido",
"api.miniapp.sendVoIPStartMessage.PROPERTY_NOT_FOUND": "No se encontró el edificio para el \"addressKey\" proporcionado",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use consistent “comunidad” terminology

Line 464 uses “edificio,” but the rest of the Spanish locale consistently refers to properties as “comunidad.” Aligning terms avoids user confusion.

✏️ Suggested wording tweak
-  "api.miniapp.sendVoIPStartMessage.PROPERTY_NOT_FOUND": "No se encontró el edificio para el \"addressKey\" proporcionado",
+  "api.miniapp.sendVoIPStartMessage.PROPERTY_NOT_FOUND": "No se encontró la comunidad para el \"addressKey\" proporcionado",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"api.miniapp.sendVoIPStartMessage.PROPERTY_NOT_FOUND": "No se encontró el edificio para el \"addressKey\" proporcionado",
"api.miniapp.sendVoIPStartMessage.PROPERTY_NOT_FOUND": "No se encontró la comunidad para el \"addressKey\" proporcionado",
🤖 Prompt for AI Agents
In `@apps/condo/lang/es/es.json` at line 464, Update the Spanish message for the
locale key "api.miniapp.sendVoIPStartMessage.PROPERTY_NOT_FOUND" to use the
consistent term "comunidad" instead of "edificio"; modify the JSON value so it
reads something like "No se encontró la comunidad para el \"addressKey\"
proporcionado" while preserving the key name and proper escaping of the quotes.

Comment thread apps/condo/schema.graphql Outdated
Comment on lines +92024 to +92108
enum VoIPType {
"""Makes mobile app use it's call app instead of B2CApp's"""
sip
}

input VoIPPanel {
"""Dtfm command for panel"""
dtfmCommand: String!

"""Name of a panel to be displayed"""
name: String!
}

input SendVoIPStartMessageData {
"""
If you want your B2CApp to handle incoming VoIP call, provide this argument. Otherwise provide all others
"""
B2CAppContext: String

"""
Unique value for each call session between panel and resident (means same for different devices also). Must be provided for correct work with multiple devices that use same voip call.F.e. to cancel calls with CANCELED_CALL_MESSAGE_PUSH messages
"""
callId: String!

"""
If "sip" was passed, mobile device will try to start native call. Info about other values will be added later
"""
voipType: VoIPType

"""Address of sip server, which device should connect to"""
voipAddress: String

"""Login for connection to sip server"""
voipLogin: String

"""Password for connection to sip server"""
voipPassword: String

"""
Panels and their commands to open. First one must be the main one. Multiple panels are in testing stage right now and may change
"""
voipPanels: [VoIPPanel]

"""Stun server url"""
stun: String

"""Preferred codec (usually vp8)"""
codec: String
}

input SendVoIPStartMessageInput {
dv: Int!
sender: SenderFieldInput!
app: B2CAppWhereUniqueInput!

"""
Should be "addressKey" of B2CAppProperty / Property for which you want to send message
"""
addressKey: String!

"""Name of unit, same as in Property map"""
unitName: String!

"""Type of unit, same as in Property map"""
unitType: AllowedVoIPMessageUnitType!
data: SendVoIPStartMessageData!
}

type SendVoIPStartMessageOutput {
"""
Count of all Organization Contacts, which we possibly could've sent messages to
"""
verifiedContactsCount: Int

"""Count of Messages that will be sent, one for each verified Resident"""
createdMessagesCount: Int

"""Count of Messages which was not created due to some internal error"""
erroredMessagesCount: Int
}

enum AllowedVoIPMessageUnitType {
flat
apartment
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n -i 'dtfm' --type=js --type=ts -g '!schema.graphql'

Repository: open-condo-software/condo

Length of output: 1447


Fix the DTMF typo in the source file.

In apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js line 95, change 'Dtfm command for panel' to 'DTMF command for panel'. This typo is propagated to the autogenerated schema.graphql.

🤖 Prompt for AI Agents
In `@apps/condo/schema.graphql` around lines 92024 - 92108, The GraphQL
description contains a typo ("Dtfm command for panel") originating from the
source constant in SendVoIPStartMessageService.js; open that file, locate the
input/field description string 'Dtfm command for panel' (used to generate the
VoIPPanel.dtfmCommand description) and change it to 'DTMF command for panel',
then re-run whatever schema generation/build step produces
apps/condo/schema.graphql so the corrected description is propagated to the
autogenerated schema.

Comment thread apps/condo/schema.ts
Comment on lines +115379 to +115384
export type VoIpPanel = {
/** Dtfm command for panel */
dtfmCommand: Scalars['String']['input'];
/** Name of a panel to be displayed */
name: Scalars['String']['input'];
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n 'dtfmCommand' --type js --type ts -C 2

Repository: open-condo-software/condo

Length of output: 3120


🏁 Script executed:

rg -n 'dtmfCommand' --type js --type ts -C 2

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

rg -n 'SendVoIPStartMessageService' --type js --type ts -C 2

Repository: open-condo-software/condo

Length of output: 3054


🏁 Script executed:

head -30 apps/condo/schema.ts

Repository: open-condo-software/condo

Length of output: 1690


🏁 Script executed:

rg -n 'voipDtfm' --type js --type ts

Repository: open-condo-software/condo

Length of output: 742


The typo dtfmCommand should be dtmfCommand — fix requires coordinated changes across multiple files.

"DTMF" (Dual-Tone Multi-Frequency) is the standard telecom acronym; this field uses the transposed spelling. While schema.ts is autogenerated and should not be edited directly, the typo originates in SendVoIPStartMessageService.js (line 97) and is consistently applied throughout the codebase—including production code, tests, and notification constants. Fixing this requires updating the source schema definition and all downstream usages (SendVoIPStartMessageService.js, SendB2CAppPushMessageService.js, test files, and notification constants) since this is part of the public GraphQL API.

🤖 Prompt for AI Agents
In `@apps/condo/schema.ts` around lines 115379 - 115384, The generated GraphQL
type VoIpPanel contains a misspelled field dtfmCommand that must be renamed to
dtmfCommand; because schema.ts is autogenerated, update the source schema/SDL or
model that defines VoIpPanel to use dtmfCommand, then update all downstream
references to the old name (e.g. SendVoIPStartMessageService.js — the call/site
around the dtfmCommand usage, SendB2CAppPushMessageService.js, notification
constants, and related tests) to use dtmfCommand so the GraphQL API and runtime
code stay consistent; run codegen/build and update any serializers/deserializers
or tests that reference dtfmCommand to the corrected symbol.

types: [
{
access: true,
type: 'enum VoIPType {' +
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not readable format for enum type

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't understand what do you mean tbh

Copy link
Copy Markdown
Contributor

@dkoviazin dkoviazin Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`
enum VoIPType {
  """Makes mobile app use it's call app instead of B2CApp's"""
  sip
}
`

},
{
access: true,
type: 'input SendVoIPStartMessageData { ' +
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use to write multi line code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will try.
Last time I checked generated docs had invalid spacings because of that

Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
function normalizeCallId (callId) {
return callId
// eslint-disable-next-line no-control-regex
.replace(/[\u0000-\u001F\s]/g, '')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to move to constant and name it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js (1)

156-184: Redundant IIFE wrapping admin.

The (async (admin) => { ... })(admin) pattern is used throughout, but admin is a const that never changes — a plain async () => { ... } closure would suffice. This pattern is repeated in multiple test blocks (lines 217, 427, 475).

Example simplification
 for (let i = 0; i < verifiedContactsCount; i++) {
-    prepareDataPromises.push((async (admin) => {
+    prepareDataPromises.push((async () => {
         const phone = createTestPhone()
         const userClient = await makeClientWithResidentUser({}, { phone })
         ...
-    })(admin))
+    })())
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js` around
lines 156 - 184, The tests create prepareDataPromises using unnecessary
immediately-invoked async functions that accept admin ((async (admin) => { ...
})(admin)) even though admin is a surrounding const; replace those IIFE patterns
with plain async arrow functions (e.g., prepareDataPromises.push(async () => {
... })) in the blocks that call createTestContact, createTestResident and
makeClientWithResidentUser so the code is simpler and functionally identical;
update all occurrences (including the ones mentioned near createTestContact,
createTestResident, and makeClientWithResidentUser) to remove the admin
parameter from the inner function and reference the outer admin directly.
apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js (2)

47-47: Pass a service identifier to getLogger().

Without a name, log entries from this module won't be easily filterable. Consider passing the service name for better observability.

Proposed fix
-const logger = getLogger()
+const logger = getLogger(SERVICE_NAME)

Note: SERVICE_NAME is defined at line 49, so you'd need to either reorder the declarations or inline the string.

-const logger = getLogger()
+const logger = getLogger('sendVoIPStartMessage')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` at line 47,
The logger is created without a service name (const logger = getLogger()), so
update it to pass a service identifier (e.g., getLogger(SERVICE_NAME) or
getLogger('SendVoIPStartMessageService')); either move the SERVICE_NAME
declaration above the logger or inline the string literal to ensure the logger
receives the service name; change the const logger assignment accordingly so all
log entries from this module are identifiable.

322-323: Static analysis: forEach callback implicitly returns a value.

The arrow function user => userIdToLocale[user.id] = user.locale returns the assigned value. Use braces to avoid the implicit return, or use a for...of loop.

Proposed fix
-                usersOfContacts.forEach(user => userIdToLocale[user.id] = user.locale)
+                usersOfContacts.forEach(user => { userIdToLocale[user.id] = user.locale })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 322 - 323, The forEach callback currently uses an arrow expression that
implicitly returns the assignment (usersOfContacts.forEach(user =>
userIdToLocale[user.id] = user.locale)), which static analysis flags; update the
loop to avoid implicit return by either using a block body
(usersOfContacts.forEach(user => { userIdToLocale[user.id] = user.locale }) ) or
replace the forEach with a for...of loop that assigns into userIdToLocale;
locate the occurrences of userIdToLocale and usersOfContacts in
SendVoIPStartMessageService.js and apply the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js`:
- Line 90: Rename all occurrences of the "Dtfm" typo to the standard "DTMF"
across schema and code: update the GraphQL type field in VoIPPanel (the "Dtfm
command" comment/field), rename the data key voipDtfmCommand to voipDTMFCommand
(and any corresponding resolver/serializer/deserializer logic), and adjust any
variable names/comments that reference Dtfm to DTMF to keep identifiers
consistent; ensure you also add a temporary compatibility layer or mapping for
incoming/outgoing payloads that still use the old voipDtfmCommand key to avoid
breaking clients while you roll out the rename.
- Around line 67-73: The CALL_DATA_NOT_PROVIDED error definition is using a
single comma-separated string in the variable array; update the variable field
in the CALL_DATA_NOT_PROVIDED object (where mutation is SERVICE_NAME) to be two
separate strings ['data', 'callData'] instead of ['data, callData'] so it
matches the pattern used by the other error definitions and correctly lists both
variables.
- Around line 362-420: The code is embedding plaintext VoIP credentials into
messageAttrs.meta.data; update the preparedDataArgs construction (used before
building messageAttrs and sent via sendMessage) to never store raw
voipLogin/voipPassword/voipAddress: instead call a secure helper (e.g.,
encryptVoipCredentials or createSecretInSecureStore) to either (a) encrypt the
credential blob and store the ciphertext in meta.data (and mark with a
version/iv) or (b) store credentials in a secure key-value store and put only a
secretId reference in meta.data; ensure
get(MESSAGE_META[VOIP_INCOMING_CALL_MESSAGE_TYPE], 'data') mapping uses the
encrypted/token field names; add validation in the webhook/push transport layer
to require TLS/encryption (config flag like requireWebhookEncryption) and fail
fast if not enabled; and ensure any logging paths that might print messageAttrs
or meta (search for sendMessage usage and any loggers around sendMessage,
message creation, or admin APIs) redact voipLogin/voipPassword/voipAddress
before logging or return only the secretId/ciphertext marker.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js`:
- Around line 86-99: Rename the test to accurately reflect the failure source:
update the test title that currently reads "should throw error if no app with
provided id" to something like "should throw error if no B2CAppProperty found
for provided app id" (or similar) so it aligns with the asserted error
ERRORS.PROPERTY_NOT_FOUND; locate the test using
sendVoIPStartMessageByTestClient and the assertion against
ERRORS.PROPERTY_NOT_FOUND and change only the description string, leaving the
test body and assertions (including references to B2CAppProperty lookup
behavior) unchanged.

---

Duplicate comments:
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js`:
- Around line 50-58: The ERRORS entry for PROPERTY_NOT_FOUND incorrectly uses
the key "query" instead of "mutation"; in the ERRORS object (look for
PROPERTY_NOT_FOUND and SERVICE_NAME in SendVoIPStartMessageService.js) replace
the "query: SERVICE_NAME" field with "mutation: SERVICE_NAME" so it matches the
other error entries for this mutation service.
- Around line 426-454: The log counters in SendVoIPStartMessageService are being
incremented twice: once inside the sendMessageResults.map (building
sendMessageStats) and again in the subsequent for loop that iterates
sendMessageStats; remove the redundant for loop that re-checks messageStat.error
and increments logInfoStats (so keep the existing counting logic inside the map
that references MESSAGE_SENDING_STATUS, resident and result) and ensure nothing
else depends on that removed loop for side effects; verify sendMessageStats and
the returned values remain unchanged.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js`:
- Line 271: The test uses new Set on msg.user (an object) so deduplication is by
reference; change the Set to use a scalar user identifier instead — e.g. replace
expect([...new Set(createdMessages.map(msg =>
msg.user))]).toHaveLength(residentsCount) with something like expect([...new
Set(createdMessages.map(msg => msg.userId ||
msg.user.id))]).toHaveLength(residentsCount) so the uniqueness check uses the
user ID rather than the user object reference.

In `@apps/condo/schema.ts`:
- Around line 115402-115407: The autogenerated type VoIpPanel has a typo: rename
the field dtfmCommand to dtmfCommand in the source GraphQL/SDL (or the
SendVoIPStartMessageService.js schema definition) and then re-run the code
generator to propagate changes; also update all usages and mappings of the
VoIpPanel type (e.g., any resolver, input builders, or client code that
references dtfmCommand) to use VoIpPanel.dtmfCommand so runtime code and types
stay in sync.

---

Nitpick comments:
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js`:
- Line 47: The logger is created without a service name (const logger =
getLogger()), so update it to pass a service identifier (e.g.,
getLogger(SERVICE_NAME) or getLogger('SendVoIPStartMessageService')); either
move the SERVICE_NAME declaration above the logger or inline the string literal
to ensure the logger receives the service name; change the const logger
assignment accordingly so all log entries from this module are identifiable.
- Around line 322-323: The forEach callback currently uses an arrow expression
that implicitly returns the assignment (usersOfContacts.forEach(user =>
userIdToLocale[user.id] = user.locale)), which static analysis flags; update the
loop to avoid implicit return by either using a block body
(usersOfContacts.forEach(user => { userIdToLocale[user.id] = user.locale }) ) or
replace the forEach with a for...of loop that assigns into userIdToLocale;
locate the occurrences of userIdToLocale and usersOfContacts in
SendVoIPStartMessageService.js and apply the change.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js`:
- Around line 156-184: The tests create prepareDataPromises using unnecessary
immediately-invoked async functions that accept admin ((async (admin) => { ...
})(admin)) even though admin is a surrounding const; replace those IIFE patterns
with plain async arrow functions (e.g., prepareDataPromises.push(async () => {
... })) in the blocks that call createTestContact, createTestResident and
makeClientWithResidentUser so the code is simpler and functionally identical;
update all occurrences (including the ones mentioned near createTestContact,
createTestResident, and makeClientWithResidentUser) to remove the admin
parameter from the inner function and reference the outer admin directly.

Comment on lines +67 to +73
CALL_DATA_NOT_PROVIDED: {
mutation: SERVICE_NAME,
variable: ['data, callData'],
type: CALL_DATA_NOT_PROVIDED_ERROR,
code: BAD_USER_INPUT,
message: '"b2cAppCallData" or "nativeCallData" or both should be provided',
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: variable array contains a single comma-separated string instead of two elements.

Line 69: ['data, callData'] is a single-element array with the string "data, callData". This should be two separate strings to match the pattern used by the other error definitions.

Proposed fix
     CALL_DATA_NOT_PROVIDED: {
         mutation: SERVICE_NAME,
-        variable: ['data, callData'],
+        variable: ['data', 'callData'],
         type: CALL_DATA_NOT_PROVIDED_ERROR,
         code: BAD_USER_INPUT,
         message: '"b2cAppCallData" or "nativeCallData" or both should be provided',
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CALL_DATA_NOT_PROVIDED: {
mutation: SERVICE_NAME,
variable: ['data, callData'],
type: CALL_DATA_NOT_PROVIDED_ERROR,
code: BAD_USER_INPUT,
message: '"b2cAppCallData" or "nativeCallData" or both should be provided',
},
CALL_DATA_NOT_PROVIDED: {
mutation: SERVICE_NAME,
variable: ['data', 'callData'],
type: CALL_DATA_NOT_PROVIDED_ERROR,
code: BAD_USER_INPUT,
message: '"b2cAppCallData" or "nativeCallData" or both should be provided',
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 67 - 73, The CALL_DATA_NOT_PROVIDED error definition is using a single
comma-separated string in the variable array; update the variable field in the
CALL_DATA_NOT_PROVIDED object (where mutation is SERVICE_NAME) to be two
separate strings ['data', 'callData'] instead of ['data, callData'] so it
matches the pattern used by the other error definitions and correctly lists both
variables.

"""
voipType: VoIPType,
*/
const MAGIC_VOIP_TYPE_CONSTANT_FOR_OLD_VERSIONS_COMPATIBILITY = 'sip' // without this constant mobile app will not try to make native call
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo in comment and field name: "Dtfm" should be "DTMF".

Line 90 has MAGIC_VOIP_TYPE_CONSTANT_FOR_OLD_VERSIONS_COMPATIBILITY which is fine, but line 103 has Dtfm command and line 390 has voipDtfmCommand — the standard abbreviation is DTMF (Dual-Tone Multi-Frequency). This typo also appears in the GraphQL schema type VoIPPanel (line 103) and the data key (line 390), so it would be a breaking change to fix later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` at line 90,
Rename all occurrences of the "Dtfm" typo to the standard "DTMF" across schema
and code: update the GraphQL type field in VoIPPanel (the "Dtfm command"
comment/field), rename the data key voipDtfmCommand to voipDTMFCommand (and any
corresponding resolver/serializer/deserializer logic), and adjust any variable
names/comments that reference Dtfm to DTMF to keep identifiers consistent;
ensure you also add a temporary compatibility layer or mapping for
incoming/outgoing payloads that still use the old voipDtfmCommand key to avoid
breaking clients while you roll out the rename.

Comment on lines +362 to +420
const startingMessagesIdsByUserIds = {}

/** @type {Array<Promise<{status, id, isDuplicateMessage}>>} */
const sendMessagePromises = verifiedResidentsWithUniqueUsers
// .filter(resident => !rateLimitsErrorsByUserIds[resident.user])
.map(async (resident) => {
// NOTE(YEgorLu): as in domains/notification/constants/config for VOIP_INCOMING_CALL_MESSAGE_TYPE
let preparedDataArgs = {
B2CAppId: b2cAppId,
B2CAppName: b2cAppName,
residentId: resident.id,
callId: callData.callId,
}

if (callData.b2cAppCallData) {
preparedDataArgs = {
...preparedDataArgs,
B2CAppContext: callData.b2cAppCallData.B2CAppContext,
}
}

if (callData.nativeCallData) {
preparedDataArgs = {
...preparedDataArgs,
voipType: MAGIC_VOIP_TYPE_CONSTANT_FOR_OLD_VERSIONS_COMPATIBILITY,
voipAddress: callData.nativeCallData.voipAddress,
voipLogin: callData.nativeCallData.voipLogin,
voipPassword: callData.nativeCallData.voipPassword,
voipDtfmCommand: callData.nativeCallData.voipPanels?.[0]?.dtfmCommand,
voipPanels: callData.nativeCallData.voipPanels,
stunServers: callData.nativeCallData.stunServers,
stun: callData.nativeCallData.stunServers?.[0],
codec: callData.nativeCallData.codec,
}
}

const requiredMetaData = get(MESSAGE_META[VOIP_INCOMING_CALL_MESSAGE_TYPE], 'data', {})
const metaData = Object.fromEntries(
Object.keys(requiredMetaData).map((key) => [key, preparedDataArgs[key]])
)

const messageAttrs = {
sender,
type: VOIP_INCOMING_CALL_MESSAGE_TYPE,
to: { user: { id: resident.user } },
meta: {
dv,
title: i18n('api.miniapp.sendVoIPStartMessage.pushData.title', { locale: userIdToLocale[resident.user] }),
body: i18n('api.miniapp.sendVoIPStartMessage.pushData.body', { locale: userIdToLocale[resident.user] }),
data: metaData,
},
}

const res = await sendMessage(context, messageAttrs)
if (res?.id) {
startingMessagesIdsByUserIds[resident.user] = res.id
}
return { resident, result: res }
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find and examine the sendMessage function implementation
fd -t f "\.js$|\.ts$" | xargs rg -l "export.*sendMessage|function sendMessage|const sendMessage" | grep -v node_modules | head -20

Repository: open-condo-software/condo

Length of output: 889


🏁 Script executed:

# Get more context about where sendMessage is defined in this file
rg -n "sendMessage" apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js -B 5 -A 5

Repository: open-condo-software/condo

Length of output: 4080


🏁 Script executed:

# Check imports in the SendVoIPStartMessageService.js file
head -50 apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js

Repository: open-condo-software/condo

Length of output: 2389


🏁 Script executed:

# Search for sendMessage definition in the codebase
rg -n "const sendMessage\s*=|function sendMessage|export.*sendMessage" --type js -g '!node_modules/**' | head -30

Repository: open-condo-software/condo

Length of output: 981


🏁 Script executed:

# Read the sendMessage function implementation
sed -n '32,120p' apps/condo/domains/notification/utils/serverSchema/index.js

Repository: open-condo-software/condo

Length of output: 3626


🏁 Script executed:

# Check MESSAGE_META structure and what fields are allowed/sanitized
rg -n "MESSAGE_META" apps/condo/domains/notification/constants/constants.js -A 30 | head -80

Repository: open-condo-software/condo

Length of output: 3753


🏁 Script executed:

# Check the Message model/schema to see how meta is stored
rg -n "meta.*Field|meta.*property" apps/condo/domains/notification/schema/Message.js -B 2 -A 2 | head -50

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Search for any logging of sensitive data in notification domain
rg -n "logger|getLogger\|logInfo\|log\.info" apps/condo/domains/notification/utils/serverSchema/index.js | head -30

Repository: open-condo-software/condo

Length of output: 158


🏁 Script executed:

# Search for VOIP_INCOMING_CALL_MESSAGE_TYPE in MESSAGE_META
rg -n "VOIP_INCOMING_CALL_MESSAGE_TYPE" apps/condo/domains/notification/constants/constants.js -A 20 | head -50

Repository: open-condo-software/condo

Length of output: 3354


🏁 Script executed:

# Find the SEND_MESSAGE mutation
rg -n "SEND_MESSAGE.*=|query SEND_MESSAGE" apps/condo/domains/notification/utils/serverSchema/index.js -A 30 | head -80

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Check the Message.js schema file
rg -n "class Message|meta.*Field" apps/condo/domains/notification/schema/Message.js -B 2 -A 5 | head -100

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Search for any logging in the Message creation/mutation handler
rg -n "logger\." apps/condo/domains/notification/schema/Message.js | head -20

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Check if there's any encryption or sanitization of meta in the notification domain
rg -n "encrypt|sanitize|redact" apps/condo/domains/notification/ -t js --type-add "js:!*.test.*" | grep -v node_modules | head -20

Repository: open-condo-software/condo

Length of output: 629


🏁 Script executed:

# Get the full VOIP_INCOMING_CALL_MESSAGE_TYPE MESSAGE_META definition
sed -n '614,650p' apps/condo/domains/notification/constants/constants.js

Repository: open-condo-software/condo

Length of output: 1374


🏁 Script executed:

# Search for SEND_MESSAGE mutation definition more broadly
rg -n "SEND_MESSAGE" apps/condo/domains/notification/utils/serverSchema/index.js

Repository: open-condo-software/condo

Length of output: 178


🏁 Script executed:

# Find the GraphQL mutation implementation for sending messages
rg -n "SendMessage.*mutation|resolve.*message.*send" apps/condo/domains/notification/schema/Message.js -i | head -20

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Check the Message model schema file completely
wc -l apps/condo/domains/notification/schema/Message.js

Repository: open-condo-software/condo

Length of output: 122


🏁 Script executed:

# Read the Message schema to see how meta is handled
head -100 apps/condo/domains/notification/schema/Message.js

Repository: open-condo-software/condo

Length of output: 3529


🏁 Script executed:

# Look for sendMessageTask to see how messages are processed
rg -n "voipLogin\|voipPassword\|voipAddress" apps/condo/domains/notification/ -t js --type-add "js:!*.test.*" | head -20

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Find where SEND_MESSAGE is imported from or defined
rg -n "SEND_MESSAGE\s*=" apps/condo/domains/notification/ -B 2 -A 2 | head -30

Repository: open-condo-software/condo

Length of output: 854


🏁 Script executed:

# Check the graphql queries/mutations file
fd -name "*.js" -path "*/notification/*" | xargs rg -l "gql\`" | head -10

Repository: open-condo-software/condo

Length of output: 723


🏁 Script executed:

# Look for the actual mutation handler/resolver
rg -n "SendMessage|sendMessage.*resolver|input SendMessage" apps/condo/domains/notification/schema/Message.js -A 10 | head -60

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Check task handler for sendMessageBatch to see how messages are processed
head -200 apps/condo/domains/notification/tasks/sendMessageBatch.js

Repository: open-condo-software/condo

Length of output: 2598


🏁 Script executed:

# Search for any sanitization or credential stripping in notification processing
rg -n "voipPassword\|voipLogin\|omit.*voip\|pick.*voip" apps/condo/domains/ -t js --type-add "js:!*.test.*" | head -20

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Check if there's database logging or audit trail of message meta
rg -n "audit\|history\|log.*meta" apps/condo/domains/notification/ -t js --type-add "js:!*.test.*" -i | head -20

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Check sendMessageBatch.helpers to see how prepareAndSendMessage works
cat apps/condo/domains/notification/tasks/sendMessageBatch.helpers.js | head -150

Repository: open-condo-software/condo

Length of output: 5824


🏁 Script executed:

# Look for how RemoteClient or push notifications are delivered
rg -n "pushToken\|RemoteClient" apps/condo/domains/notification/adapters/ -t js | head -30

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Check if meta is filtered/sanitized before sending to clients
rg -n "meta.*filter\|meta.*omit\|meta.*pick\|sanitize" apps/condo/domains/notification/ -t js --type-add "js:!*.test.*" | head -20

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Look for webhook/push adapter implementation to see how meta is transmitted
head -150 apps/condo/domains/notification/adapters/webhookAdapter.js

Repository: open-condo-software/condo

Length of output: 5931


🏁 Script executed:

# Search for how the notification service actually sends data to devices/clients
rg -n "sendPush\|firebase\|fcm\|apns" apps/condo/domains/notification/ -t js --type-add "js:!*.test.*" -i | head -20

Repository: open-condo-software/condo

Length of output: 51


🏁 Script executed:

# Check if there's any documentation about security/encryption in notification domain
find apps/condo/domains/notification -name "*.md" -o -name "README*" | xargs cat 2>/dev/null | head -100

Repository: open-condo-software/condo

Length of output: 2330


VoIP credentials in meta.data require protection against unauthorized access and logging.

VoIP credentials (voipLogin, voipPassword, voipAddress) are persisted in plaintext in the Message table's meta JSON field. The notification system provides optional webhook encryption but does not enforce it by default—production deployments using webhooks will transmit credentials unencrypted. Implement:

  1. Encrypt credentials at rest in the Message.meta field
  2. Enforce encryption in transit for all transport layers (webhooks, push services)
  3. Exclude credentials from debug/logging output if messages are exposed via APIs or admin interfaces
  4. Consider storing credentials separately (e.g., in a secure key-value store referenced by ID) rather than embedding them in every message record
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 362 - 420, The code is embedding plaintext VoIP credentials into
messageAttrs.meta.data; update the preparedDataArgs construction (used before
building messageAttrs and sent via sendMessage) to never store raw
voipLogin/voipPassword/voipAddress: instead call a secure helper (e.g.,
encryptVoipCredentials or createSecretInSecureStore) to either (a) encrypt the
credential blob and store the ciphertext in meta.data (and mark with a
version/iv) or (b) store credentials in a secure key-value store and put only a
secretId reference in meta.data; ensure
get(MESSAGE_META[VOIP_INCOMING_CALL_MESSAGE_TYPE], 'data') mapping uses the
encrypted/token field names; add validation in the webhook/push transport layer
to require TLS/encryption (config flag like requireWebhookEncryption) and fail
fast if not enabled; and ensure any logging paths that might print messageAttrs
or meta (search for sendMessage usage and any loggers around sendMessage,
message creation, or admin APIs) redact voipLogin/voipPassword/voipAddress
before logging or return only the secretId/ciphertext marker.

Comment on lines +86 to +99
test('should throw error if no app with provided id', async () => {
await expectToThrowGQLErrorToResult(async () => {
await sendVoIPStartMessageByTestClient(admin, {
app: { id: faker.datatype.uuid() },
addressKey: faker.datatype.uuid(),
unitName: faker.random.alphaNumeric(3),
unitType: FLAT_UNIT_TYPE,
callData: {
callId: faker.datatype.uuid(),
b2cAppCallData: { B2CAppContext: '' },
},
})
}, ERRORS.PROPERTY_NOT_FOUND)
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Misleading test name: error is PROPERTY_NOT_FOUND, not "no app".

The test sends a nonexistent app.id but asserts ERRORS.PROPERTY_NOT_FOUND. The name "should throw error if no app with provided id" suggests app-existence validation, but the service actually fails on the B2CAppProperty lookup. Consider renaming to something like 'should throw error if no b2cappproperty found for provided app id' to match the actual behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js` around
lines 86 - 99, Rename the test to accurately reflect the failure source: update
the test title that currently reads "should throw error if no app with provided
id" to something like "should throw error if no B2CAppProperty found for
provided app id" (or similar) so it aligns with the asserted error
ERRORS.PROPERTY_NOT_FOUND; locate the test using
sendVoIPStartMessageByTestClient and the assertion against
ERRORS.PROPERTY_NOT_FOUND and change only the description string, leaving the
test body and assertions (including references to B2CAppProperty lookup
behavior) unchanged.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js (1)

92-94: Use the standard entity field in this log record.

entityName is outside the repo logging schema, so consumers keyed on entity will miss these entries.

Suggested fix
 const logInfo = ({ b2cAppId, callId, stats, errors }) => {
-    logger.info({ msg: `${SERVICE_NAME} stats`, entityName: 'B2CApp', entityId: b2cAppId, data: { callId, stats }, err: errors })
+    logger.info({ msg: `${SERVICE_NAME} stats`, entity: 'B2CApp', entityId: b2cAppId, data: { callId, stats }, err: errors })
 }

As per coding guidelines, Use standard logging fields: msg, data, entityId, entity, count, status, and err instead of custom field names.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 92 - 94, The log record in the logInfo function uses a non-standard field
entityName; update logger.info call in logInfo to use the standard fields: msg,
data, entityId, entity, and err (e.g., set entity to 'B2CApp'), keeping msg as
`${SERVICE_NAME} stats` and preserving data: { callId, stats } and err: errors
so consumers keyed on entity will surface these entries correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js`:
- Around line 382-390: When building preparedDataArgs in the
SendVoIPStartMessageService code path that handles callData.nativeCallData, add
an explicit check for callData.nativeCallData.voipPanels?.length > 0 and only
read voipPanels[0].dtfmCommand (and include voipPanels in preparedDataArgs) when
that length check passes; do not blindly access voipPanels?.[0] or include an
empty voipPanels array — alternatively reject/throw a validation error when
voipPanels is an empty array. Update the assignment that sets voipDtfmCommand
and voipPanels (the block that also sets voipType using
MAGIC_VOIP_TYPE_CONSTANT_FOR_OLD_VERSIONS_COMPATIBILITY) to guard on the
non-empty condition.
- Around line 291-296: Remove the manual "deletedAt: null" filters so the
soft-delete plugin can apply automatic filtering; specifically, in the find call
that sets residentsOnUnit (the find('Resident', { unitName_i, unitType,
deletedAt: null, property: { id_in: [...] } }) created from
verifiedContactsOnUnit) remove the root deletedAt: null and do the same for the
other similar find/query at the indicated block around lines 335-337—do not add
deletedAt to nested property or b2cApp filters either, let the schema plugin
handle all soft-delete filtering.
- Around line 412-415: The code records any sendMessage(...) result id into
startingMessagesIdsByUserIds[resident.user] but only calls setCallStatus(...)
when a message was treated as a fresh success, which means duplicate-only
retries won't persist the call status; change the logic so that whenever a
message id is available from sendMessage (including duplicates) you
persist/update the call status (call setCallStatus with the appropriate
resident/user and message id) instead of gating it only on "fresh" success —
update both the block around sendMessage -> startingMessagesIdsByUserIds and the
similar block at lines 453-463 to always persist status when res?.id is present.

---

Nitpick comments:
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js`:
- Around line 92-94: The log record in the logInfo function uses a non-standard
field entityName; update logger.info call in logInfo to use the standard fields:
msg, data, entityId, entity, and err (e.g., set entity to 'B2CApp'), keeping msg
as `${SERVICE_NAME} stats` and preserving data: { callId, stats } and err:
errors so consumers keyed on entity will surface these entries correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac973bb0-fcba-4c6c-9134-6394b83a36e6

📥 Commits

Reviewing files that changed from the base of the PR and between e0b83c3 and ce74fca.

📒 Files selected for processing (4)
  • apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js
  • apps/condo/lang/en/en.json
  • apps/condo/lang/es/es.json
  • apps/condo/lang/ru/ru.json
✅ Files skipped from review due to trivial changes (2)
  • apps/condo/lang/ru/ru.json
  • apps/condo/lang/en/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/condo/lang/es/es.json

Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment on lines +382 to +390
if (callData.nativeCallData) {
preparedDataArgs = {
...preparedDataArgs,
voipType: MAGIC_VOIP_TYPE_CONSTANT_FOR_OLD_VERSIONS_COMPATIBILITY,
voipAddress: callData.nativeCallData.voipAddress,
voipLogin: callData.nativeCallData.voipLogin,
voipPassword: callData.nativeCallData.voipPassword,
voipDtfmCommand: callData.nativeCallData.voipPanels?.[0]?.dtfmCommand,
voipPanels: callData.nativeCallData.voipPanels,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject empty voipPanels before reading the first panel.

This path always uses the first panel's dtfmCommand. If a client sends voipPanels: [], the mutation still builds nativeCallData, but the payload is incomplete because the main panel command becomes undefined. Add an explicit length check before building preparedDataArgs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 382 - 390, When building preparedDataArgs in the
SendVoIPStartMessageService code path that handles callData.nativeCallData, add
an explicit check for callData.nativeCallData.voipPanels?.length > 0 and only
read voipPanels[0].dtfmCommand (and include voipPanels in preparedDataArgs) when
that length check passes; do not blindly access voipPanels?.[0] or include an
empty voipPanels array — alternatively reject/throw a validation error when
voipPanels is an empty array. Update the assignment that sets voipDtfmCommand
and voipPanels (the block that also sets voipType using
MAGIC_VOIP_TYPE_CONSTANT_FOR_OLD_VERSIONS_COMPATIBILITY) to guard on the
non-empty condition.

Comment on lines +412 to +415
const res = await sendMessage(context, messageAttrs)
if (res?.id) {
startingMessagesIdsByUserIds[resident.user] = res.id
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

A duplicate-only retry can never restore missing call status.

Any sendMessage() result with an id is recorded here, including duplicates, but setCallStatus() only runs when at least one message is treated as success. If a previous attempt created messages and then failed before caching, the retry will likely hit only duplicates and skip the cache write again, so cancel handling stays broken. Persist the status whenever message IDs are available, not only when a fresh message was created.

Also applies to: 453-463

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 412 - 415, The code records any sendMessage(...) result id into
startingMessagesIdsByUserIds[resident.user] but only calls setCallStatus(...)
when a message was treated as a fresh success, which means duplicate-only
retries won't persist the call status; change the logic so that whenever a
message id is available from sendMessage (including duplicates) you
persist/update the call status (call setCallStatus with the appropriate
resident/user and message id) instead of gating it only on "fresh" success —
update both the block around sendMessage -> startingMessagesIdsByUserIds and the
similar block at lines 453-463 to always persist status when res?.id is present.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js (1)

408-410: Redundant filter: actors already guaranteed to have resident, contact, and user.

The actors array was filtered on line 346 to only include items with actor.resident. Earlier filtering at line 333 ensured actor.user exists. The filter on line 409 actor => !!actor.resident && !!actor.contact && !!actor.user is defensive but redundant.

Suggested simplification
-                const sendMessagePromises = actors
-                    .filter(actor => !!actor.resident && !!actor.contact && !!actor.user)
-                    .map(async ({ contact, resident, user }) => {
+                const sendMessagePromises = actors.map(async ({ contact, resident, user }) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 408 - 410, The filter in the sendMessagePromises construction is redundant
because the actors array has already been filtered to guarantee resident,
contact, and user; remove the .filter(actor => !!actor.resident &&
!!actor.contact && !!actor.user) and directly map over actors (actors.map(async
({ contact, resident, user }) => {...})), keeping the same async mapping logic
so sendMessagePromises, the mapping callback, and any uses of
contact/resident/user remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/condo/domains/miniapp/schema/sendVoIPStartMessageService.spec.js`:
- Around line 273-280: The test duplicates the same assertion on
b2cAppVoIPTypeMessages (expect(b2cAppVoIPTypeMessages).toHaveLength(1)) twice;
remove the redundant first assertion (the standalone expect) and keep the single
assertion inside the try-catch (or conversely remove the try-catch and keep one
assert), so only one expect(b2cAppVoIPTypeMessages).toHaveLength(1) remains;
update any surrounding comments/logging accordingly to avoid duplicate checks in
the test for b2cAppVoIPTypeMessages.

---

Nitpick comments:
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js`:
- Around line 408-410: The filter in the sendMessagePromises construction is
redundant because the actors array has already been filtered to guarantee
resident, contact, and user; remove the .filter(actor => !!actor.resident &&
!!actor.contact && !!actor.user) and directly map over actors (actors.map(async
({ contact, resident, user }) => {...})), keeping the same async mapping logic
so sendMessagePromises, the mapping callback, and any uses of
contact/resident/user remain unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 06def00b-86ea-42c4-acfb-d10dc7311700

📥 Commits

Reviewing files that changed from the base of the PR and between ce74fca and 11c5787.

📒 Files selected for processing (6)
  • apps/condo/domains/miniapp/constants.js
  • apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js
  • apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js
  • apps/condo/domains/miniapp/schema/sendVoIPStartMessageService.spec.js
  • apps/condo/domains/notification/constants/constants.js
  • apps/condo/schema.graphql
✅ Files skipped from review due to trivial changes (2)
  • apps/condo/domains/notification/constants/constants.js
  • apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/condo/domains/miniapp/constants.js

Comment on lines +273 to +280
const b2cAppVoIPTypeMessages = createdMessages.filter(msg => !!msg.meta.data.B2CAppContext)
expect(b2cAppVoIPTypeMessages).toHaveLength(1)
try {
expect(b2cAppVoIPTypeMessages).toHaveLength(1)
} catch (err) {
console.error('b2cAppVoIPTypeMessages fail')
throw err
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Duplicate assertion: toHaveLength(1) is checked twice.

Line 274 and line 276 both assert expect(b2cAppVoIPTypeMessages).toHaveLength(1). The first assertion (line 274) is outside the try-catch, making the one inside (line 276) redundant.

Suggested fix
                const b2cAppVoIPTypeMessages = createdMessages.filter(msg => !!msg.meta.data.B2CAppContext)
-                expect(b2cAppVoIPTypeMessages).toHaveLength(1)
                try {
                    expect(b2cAppVoIPTypeMessages).toHaveLength(1)
                } catch (err) {
                    console.error('b2cAppVoIPTypeMessages fail')
                    throw err
                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const b2cAppVoIPTypeMessages = createdMessages.filter(msg => !!msg.meta.data.B2CAppContext)
expect(b2cAppVoIPTypeMessages).toHaveLength(1)
try {
expect(b2cAppVoIPTypeMessages).toHaveLength(1)
} catch (err) {
console.error('b2cAppVoIPTypeMessages fail')
throw err
}
const b2cAppVoIPTypeMessages = createdMessages.filter(msg => !!msg.meta.data.B2CAppContext)
try {
expect(b2cAppVoIPTypeMessages).toHaveLength(1)
} catch (err) {
console.error('b2cAppVoIPTypeMessages fail')
throw err
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/sendVoIPStartMessageService.spec.js` around
lines 273 - 280, The test duplicates the same assertion on
b2cAppVoIPTypeMessages (expect(b2cAppVoIPTypeMessages).toHaveLength(1)) twice;
remove the redundant first assertion (the standalone expect) and keep the single
assertion inside the try-catch (or conversely remove the try-catch and keep one
assert), so only one expect(b2cAppVoIPTypeMessages).toHaveLength(1) remains;
update any surrounding comments/logging accordingly to avoid duplicate checks in
the test for b2cAppVoIPTypeMessages.

Comment thread apps/condo/domains/miniapp/access/SendVoIPStartMessageService.js
* If debug app is set and debug app settings are configured, then user can send push messages without creating B2CApp first.
* This is useful for testing and development, but it should be turned off on production
*/
const DEBUG_APP_ID = conf.MINIAPP_PUSH_MESSAGE_DEBUG_APP_ID
Copy link
Copy Markdown
Member

@SavelevMatthew SavelevMatthew Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be here? I thought it was some legacy or smth

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't exactly know, looks like it is being used for local development of miniapps

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe its worth moving into util, so it can be reused across multiple mutations / services

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lol I just understood that in this mutation, unlike sendB2CAppMessage, existence of b2c app and related items is required, so there is no way that non existent debug app would work. Removing it

Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js
Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
}

// NOTE(YEgorLu): doing same as Resident.isVerifiedByManagingCompany virtual field
const usersOfContacts = await find('User', {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get it, why not just fetching residents on address?

Btw, fetching users by phone seems strange, you have ids from residents, why phones

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't need ALL residents. I need only VERIFIED residents.
Verified means that I must find relations Resident <-> Contact
They relate only with user: Resident.user <-> User.id, User.phone <-> Contact.phone, Resident.address <-> Contact.address.

It can be started from Contacts, it can be started from Residents, but anyway I must in the end relate one to other using phone number and User.

Copy link
Copy Markdown
Member

@SavelevMatthew SavelevMatthew Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressKey / unitName / unitType

-> Residents(where: { addressKey, unitName, unitType}) -> const usersIds = residents.map(r => r.user)
-> Users(where: { id_in: [] }) -> ids (for push), + phones
-> Find single property (Property util)
-> Contacts(where: { property: { id }, unitName, unitType, addressKey })

Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment thread apps/condo/domains/miniapp/constants.js Outdated
Comment thread apps/condo/domains/miniapp/constants.js Outdated
@YEgorLu YEgorLu force-pushed the feat/condo/DOMA-12905/send-voip-start-push-message branch from 11c5787 to d87acf0 Compare April 21, 2026 12:43
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (5)
apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js (5)

60-66: ⚠️ Potential issue | 🟡 Minor

Fix the malformed error variable path.

['data, callData'] is one string, not the two path segments expected by the error metadata.

Proposed fix
     CALL_DATA_NOT_PROVIDED: {
         mutation: SERVICE_NAME,
-        variable: ['data, callData'],
+        variable: ['data', 'callData'],
         type: CALL_DATA_NOT_PROVIDED_ERROR,
         code: BAD_USER_INPUT,
         message: '"b2cAppCallData" or "nativeCallData" or both should be provided',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 60 - 66, The CALL_DATA_NOT_PROVIDED error metadata currently has a
malformed variable path ['data, callData'] (one string) — update the variable
array in the CALL_DATA_NOT_PROVIDED entry so each path segment is its own string
(e.g., ['data', 'callData']) so the error system can correctly reference the
fields; keep the other fields (mutation: SERVICE_NAME, type:
CALL_DATA_NOT_PROVIDED_ERROR, code: BAD_USER_INPUT, message) unchanged.

153-165: ⚠️ Potential issue | 🟠 Major

Reject empty voipPanels before building native call data.

[SendVoIPStartMessageVoIPPanelParameters!]! still allows [], and Line 159 then sends voipDtfmCommand: undefined. Add an explicit non-empty check before accepting nativeCallData.

Validation sketch
                 if (!callData.b2cAppCallData && !callData.nativeCallData) {
                     throw new GQLError(ERRORS.CALL_DATA_NOT_PROVIDED, context)
                 }
+                if (callData.nativeCallData && !callData.nativeCallData.voipPanels.length) {
+                    throw new GQLError(ERRORS.CALL_DATA_NOT_PROVIDED, context)
+                }

Also applies to: 417-417

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 153 - 165, The code assigns voipPanels and voipDtfmCommand from
nativeCallData without rejecting an empty array, so add an explicit non-empty
check on nativeCallData.voipPanels before building preparedDataArgs: if
nativeCallData.voipPanels is undefined or nativeCallData.voipPanels.length ===
0, either throw a validation error or avoid copying voipPanels and
voipDtfmCommand into preparedDataArgs (i.e., only set voipPanels and
voipDtfmCommand when the array is non-empty). Update the assignment site that
constructs preparedDataArgs (and the similar case around the other occurrence
noted) to use this guard so voipDtfmCommand is never set to undefined.

82-86: ⚠️ Potential issue | 🟠 Major

Let the soft-delete plugin own these filters.

Manual deletedAt filters can disable automatic nested soft-delete filtering, making every inner relation filter easy to miss. Prefer removing these manual deletedAt clauses and relying on the plugin.

Based on learnings, all schemas have a "softDeleted" plugin that automatically sets deletedAt filters in queries and inner filters. Manually adding a deletedAt filter disables this automatic behavior and requires setting deletedAt filters for all inner entities.

Also applies to: 213-219, 235-240, 290-294, 312-316

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 82 - 86, The query that fetches AppMessageSetting (the getByCondition call
referencing AppMessageSetting and VOIP_INCOMING_CALL_MESSAGE_TYPE) includes a
manual deletedAt filter which disables the schema's soft-delete plugin; remove
the explicit deletedAt: null from this getByCondition call and from the other
similar getByCondition/where clauses in this file so the softDeleted plugin can
provide automatic nested deletedAt filtering instead (search for
getByCondition/where usages that reference AppMessageSetting,
VOIP_INCOMING_CALL_MESSAGE_TYPE, or related entity filters and drop their manual
deletedAt clauses).

324-356: ⚠️ Potential issue | 🟡 Minor

Remove the second counter pass.

The .map() already updates createdMessagesCount, erroredMessagesCount, and createMessageErrors; the following loop doubles the logged counters.

Proposed fix
     const sendMessageStats = sendMessagePromisesResults.map(promiseResult => {
         if (promiseResult.status === 'rejected') {
             logContext.logInfoStats.erroredMessagesCount++
             logContext.logInfoStats.createMessageErrors.push(promiseResult.reason)
@@
         logContext.logInfoStats.createdMessagesCount++
         return result
     })
-
-    for (const messageStat of sendMessageStats) {
-        if (messageStat.error) {
-            logContext.logInfoStats.erroredMessagesCount++
-            logContext.logInfoStats.createMessageErrors.push(messageStat.error)
-            continue
-        }
-        logContext.logInfoStats.createdMessagesCount++
-    }
 
     return sendMessageStats
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 324 - 356, parseSendMessageResults is double-counting because the initial
.map over sendMessagePromisesResults already updates logContext.logInfoStats
(createdMessagesCount, erroredMessagesCount, createMessageErrors) and the
subsequent for...of loop repeats those increments; remove the second pass
entirely (delete the for (const messageStat of sendMessageStats) { ... } block)
so counts and errors are only updated once, leaving the .map logic and the final
return sendMessageStats intact; ensure MESSAGE_SENDING_STATUS logic and returned
objects from the .map remain unchanged.

153-165: ⚠️ Potential issue | 🟠 Major

Avoid persisting raw VoIP credentials in message metadata.

voipLogin, voipPassword, and voipAddress are copied into Message.meta.data. Store an encrypted blob or a short-lived secret reference instead of plaintext credentials.

Also applies to: 178-190

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 153 - 165, The code is persisting raw VoIP credentials (voipLogin,
voipPassword, voipAddress) into Message.meta.data via preparedDataArgs; instead,
remove those plain fields from preparedDataArgs and replace them with either an
encrypted blob or a short-lived secret reference: call a helper (e.g.,
encryptVoIPCredentials or SecretStore.saveSecret) with
callData.nativeCallData.{voipAddress,voipLogin,voipPassword,voipPanels,stunServers}
to obtain an encryptedPayload or secretId, include only
encryptedPayload/secretId and a TTL in preparedDataArgs, and ensure
omit(customVoIPValues, 'voipType') also omits credential keys; apply the same
change to the corresponding block referenced at lines 178-190 so no plaintext
credentials are ever copied into Message.meta.data.
🧹 Nitpick comments (1)
apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js (1)

77-79: Use the standard structured logging field name.

entityName is a custom field; use entity so logs stay queryable with the project’s standard schema.

Logging field fix
 function logInfo ({ b2cAppId, callId, stats, err }) {
-    logger.info({ msg: `${SERVICE_NAME} stats`, entityName: 'B2CApp', entityId: b2cAppId, data: { callId, stats }, err: err })
+    logger.info({ msg: `${SERVICE_NAME} stats`, entity: 'B2CApp', entityId: b2cAppId, data: { callId, stats }, err })
 }

As per coding guidelines, Use standard logging fields: msg, data, entityId, entity, count, status, and err instead of custom field names.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 77 - 79, In the logInfo function replace the custom field entityName with
the standard field entity so logs follow the project's schema: update the
logger.info call inside function logInfo (which references SERVICE_NAME,
b2cAppId, callId, stats, err) to emit msg, data, entityId, entity and err (e.g.,
entity: 'B2CApp') rather than entityName; keep the existing msg, data, entityId
and err payloads intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/condo/domains/miniapp/schema/B2CAppAccessRightSet.test.js`:
- Around line 314-315: Remove trailing semicolons from the two setup statements:
the call that assigns serviceUser from makeClientWithServiceUser() and the array
destructure that assigns b2cApp from createTestB2CApp(admin); update the lines
with serviceUser = await makeClientWithServiceUser() and [b2cApp] = await
createTestB2CApp(admin) to omit the semicolons to comply with the repo's
no-semicolon JS/TS style rule.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js`:
- Around line 504-506: The code currently accepts callId values that are empty
or whitespace and proceeds to send VoIP messages even when setCallStatus()
fails; update SendVoIPStartMessageService.js to validate callId before changing
status or sending messages: trim callId (from the incoming callData.callId) and
if it's empty throw a GQLError (same style as the existing throw using
ERRORS.CALL_DATA_NOT_PROVIDED and context) or otherwise return early;
additionally, after calling setCallStatus() (the setCallStatus function call
referenced around lines 543-553), check its boolean result and abort sending
VoIP messages if it returns false (throw a GQLError or return) so messages are
not sent for invalid/missing call IDs. Ensure you update both the initial
callData presence check block and the post-setCallStatus handling to use this
validation.

In `@apps/condo/domains/miniapp/utils/voip.js`:
- Around line 14-28: The bug is that buildKey uses a normalized callId but
validateCallId checks the raw value, causing collisions (e.g., "abc def" vs
"abcdef") and empty suffixes; fix by validating the normalized value before
building the key: update validateCallId (or add a new validateNormalizedCallId)
to call normalizeCallId(callId) and perform the string checks (non-empty and
length < 300) on the normalized result, and modify buildKey to normalize first
and then call the validator (using symbols normalizeCallId, validateCallId,
buildKey, KEY_PREFIX, Buffer.from) so no key is built from an unvalidated/empty
normalized suffix.

---

Duplicate comments:
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js`:
- Around line 60-66: The CALL_DATA_NOT_PROVIDED error metadata currently has a
malformed variable path ['data, callData'] (one string) — update the variable
array in the CALL_DATA_NOT_PROVIDED entry so each path segment is its own string
(e.g., ['data', 'callData']) so the error system can correctly reference the
fields; keep the other fields (mutation: SERVICE_NAME, type:
CALL_DATA_NOT_PROVIDED_ERROR, code: BAD_USER_INPUT, message) unchanged.
- Around line 153-165: The code assigns voipPanels and voipDtfmCommand from
nativeCallData without rejecting an empty array, so add an explicit non-empty
check on nativeCallData.voipPanels before building preparedDataArgs: if
nativeCallData.voipPanels is undefined or nativeCallData.voipPanels.length ===
0, either throw a validation error or avoid copying voipPanels and
voipDtfmCommand into preparedDataArgs (i.e., only set voipPanels and
voipDtfmCommand when the array is non-empty). Update the assignment site that
constructs preparedDataArgs (and the similar case around the other occurrence
noted) to use this guard so voipDtfmCommand is never set to undefined.
- Around line 82-86: The query that fetches AppMessageSetting (the
getByCondition call referencing AppMessageSetting and
VOIP_INCOMING_CALL_MESSAGE_TYPE) includes a manual deletedAt filter which
disables the schema's soft-delete plugin; remove the explicit deletedAt: null
from this getByCondition call and from the other similar getByCondition/where
clauses in this file so the softDeleted plugin can provide automatic nested
deletedAt filtering instead (search for getByCondition/where usages that
reference AppMessageSetting, VOIP_INCOMING_CALL_MESSAGE_TYPE, or related entity
filters and drop their manual deletedAt clauses).
- Around line 324-356: parseSendMessageResults is double-counting because the
initial .map over sendMessagePromisesResults already updates
logContext.logInfoStats (createdMessagesCount, erroredMessagesCount,
createMessageErrors) and the subsequent for...of loop repeats those increments;
remove the second pass entirely (delete the for (const messageStat of
sendMessageStats) { ... } block) so counts and errors are only updated once,
leaving the .map logic and the final return sendMessageStats intact; ensure
MESSAGE_SENDING_STATUS logic and returned objects from the .map remain
unchanged.
- Around line 153-165: The code is persisting raw VoIP credentials (voipLogin,
voipPassword, voipAddress) into Message.meta.data via preparedDataArgs; instead,
remove those plain fields from preparedDataArgs and replace them with either an
encrypted blob or a short-lived secret reference: call a helper (e.g.,
encryptVoIPCredentials or SecretStore.saveSecret) with
callData.nativeCallData.{voipAddress,voipLogin,voipPassword,voipPanels,stunServers}
to obtain an encryptedPayload or secretId, include only
encryptedPayload/secretId and a TTL in preparedDataArgs, and ensure
omit(customVoIPValues, 'voipType') also omits credential keys; apply the same
change to the corresponding block referenced at lines 178-190 so no plaintext
credentials are ever copied into Message.meta.data.

---

Nitpick comments:
In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js`:
- Around line 77-79: In the logInfo function replace the custom field entityName
with the standard field entity so logs follow the project's schema: update the
logger.info call inside function logInfo (which references SERVICE_NAME,
b2cAppId, callId, stats, err) to emit msg, data, entityId, entity and err (e.g.,
entity: 'B2CApp') rather than entityName; keep the existing msg, data, entityId
and err payloads intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c4896dd-07de-4e1d-8cd5-82f321451c5b

📥 Commits

Reviewing files that changed from the base of the PR and between 11c5787 and d87acf0.

📒 Files selected for processing (11)
  • apps/condo/domains/miniapp/access/SendVoIPStartMessageService.js
  • apps/condo/domains/miniapp/constants.js
  • apps/condo/domains/miniapp/gql.js
  • apps/condo/domains/miniapp/schema/B2CAppAccessRightSet.test.js
  • apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js
  • apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js
  • apps/condo/domains/miniapp/schema/index.js
  • apps/condo/domains/miniapp/utils/b2cAppServiceUserAccess/config.js
  • apps/condo/domains/miniapp/utils/b2cAppServiceUserAccess/server.utils.js
  • apps/condo/domains/miniapp/utils/testSchema/index.js
  • apps/condo/domains/miniapp/utils/voip.js
✅ Files skipped from review due to trivial changes (2)
  • apps/condo/domains/miniapp/schema/index.js
  • apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.test.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/condo/domains/miniapp/constants.js
  • apps/condo/domains/miniapp/utils/testSchema/index.js
  • apps/condo/domains/miniapp/access/SendVoIPStartMessageService.js

Comment on lines 314 to +315
serviceUser = await makeClientWithServiceUser();
[b2cApp] = await createTestB2CApp(admin)
await createTestB2CAppAccessRight(admin, serviceUser.user, b2cApp)
[b2cApp] = await createTestB2CApp(admin);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove semicolons from the changed setup lines.

These two lines violate the repo’s JavaScript style rule.

Style fix
-            serviceUser = await makeClientWithServiceUser();
-            [b2cApp] = await createTestB2CApp(admin);
+            serviceUser = await makeClientWithServiceUser()
+            [b2cApp] = await createTestB2CApp(admin)

As per coding guidelines, Never use semicolons in JavaScript/TypeScript code.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
serviceUser = await makeClientWithServiceUser();
[b2cApp] = await createTestB2CApp(admin)
await createTestB2CAppAccessRight(admin, serviceUser.user, b2cApp)
[b2cApp] = await createTestB2CApp(admin);
serviceUser = await makeClientWithServiceUser()
[b2cApp] = await createTestB2CApp(admin)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/B2CAppAccessRightSet.test.js` around lines
314 - 315, Remove trailing semicolons from the two setup statements: the call
that assigns serviceUser from makeClientWithServiceUser() and the array
destructure that assigns b2cApp from createTestB2CApp(admin); update the lines
with serviceUser = await makeClientWithServiceUser() and [b2cApp] = await
createTestB2CApp(admin) to omit the semicolons to comply with the repo's
no-semicolon JS/TS style rule.

Comment on lines +504 to +506
if (!callData.b2cAppCallData && !callData.nativeCallData) {
throw new GQLError(ERRORS.CALL_DATA_NOT_PROVIDED, context)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate callId before sending messages.

String! allows '' and whitespace-only values. In those cases setCallStatus() can return false, but the mutation still sends VoIP messages, so cancel handling cannot find the call status later.

Suggested direction
                 if (!callData.b2cAppCallData && !callData.nativeCallData) {
                     throw new GQLError(ERRORS.CALL_DATA_NOT_PROVIDED, context)
                 }
+                if (typeof callData.callId !== 'string' || callData.callId.trim().length === 0 || callData.callId.length >= 300) {
+                    throw new GQLError(ERRORS.CALL_DATA_NOT_PROVIDED, context)
+                }

Also applies to: 543-553

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js` around
lines 504 - 506, The code currently accepts callId values that are empty or
whitespace and proceeds to send VoIP messages even when setCallStatus() fails;
update SendVoIPStartMessageService.js to validate callId before changing status
or sending messages: trim callId (from the incoming callData.callId) and if it's
empty throw a GQLError (same style as the existing throw using
ERRORS.CALL_DATA_NOT_PROVIDED and context) or otherwise return early;
additionally, after calling setCallStatus() (the setCallStatus function call
referenced around lines 543-553), check its boolean result and abort sending
VoIP messages if it returns false (throw a GQLError or return) so messages are
not sent for invalid/missing call IDs. Ensure you update both the initial
callData presence check block and the post-setCallStatus handling to use this
validation.

Comment on lines +14 to +28
function validateCallId (callId) {
return typeof callId === 'string'
&& callId.length > 0
&& callId.length < 300
}

function normalizeCallId (callId) {
return callId
// eslint-disable-next-line no-control-regex
.replace(/[\u0000-\u001F\s]/g, '')
}

function buildKey (b2cAppId, callId) {
return [KEY_PREFIX, b2cAppId, Buffer.from(callId).toString('base64')].join(':')
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate the normalized call ID to avoid Redis key collisions.

Line 33 and Line 43 validate the raw value, but Line 35 and Line 44 build the key from the normalized value. That makes distinct IDs like 'abc def' and 'abcdef' collide, and whitespace-only IDs normalize to an empty key suffix.

Proposed fix
-function validateCallId (callId) {
+function normalizeCallId (callId) {
+    if (typeof callId !== 'string') return null
+    return callId
+        // eslint-disable-next-line no-control-regex
+        .replace(/[\u0000-\u001F\s]/g, '')
+}
+
+function validateCallId (callId) {
     return typeof callId === 'string'
         && callId.length > 0
         && callId.length < 300
 }
 
-function normalizeCallId (callId) {
-    return callId
-        // eslint-disable-next-line no-control-regex
-        .replace(/[\u0000-\u001F\s]/g, '')
-}
-
 function buildKey (b2cAppId, callId) {
     return [KEY_PREFIX, b2cAppId, Buffer.from(callId).toString('base64')].join(':')
 }
@@
 async function setCallStatus ({ b2cAppId, callId, status, startingMessagesIdsByUserIds }) {
-    if (!validateCallId(callId)) return false
+    const normalizedCallId = normalizeCallId(callId)
+    if (!validateCallId(normalizedCallId)) return false
     return kv.set(
-        buildKey(b2cAppId, normalizeCallId(callId)),
+        buildKey(b2cAppId, normalizedCallId),
         JSON.stringify({ status, startingMessagesIdsByUserIds }),
         'EX',
         CALL_STATUS_TTL_IN_SECONDS,
@@
 async function getCallStatus ({ b2cAppId, callId }) {
-    if (!validateCallId(callId)) return null
-    const res = await kv.get(buildKey(b2cAppId, normalizeCallId(callId)))
+    const normalizedCallId = normalizeCallId(callId)
+    if (!validateCallId(normalizedCallId)) return null
+    const res = await kv.get(buildKey(b2cAppId, normalizedCallId))

Also applies to: 32-44

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/condo/domains/miniapp/utils/voip.js` around lines 14 - 28, The bug is
that buildKey uses a normalized callId but validateCallId checks the raw value,
causing collisions (e.g., "abc def" vs "abcdef") and empty suffixes; fix by
validating the normalized value before building the key: update validateCallId
(or add a new validateNormalizedCallId) to call normalizeCallId(callId) and
perform the string checks (non-empty and length < 300) on the normalized result,
and modify buildKey to normalize first and then call the validator (using
symbols normalizeCallId, validateCallId, buildKey, KEY_PREFIX, Buffer.from) so
no key is built from an unvalidated/empty normalized suffix.

const { makeClientWithNewRegisteredAndLoggedInUser, makeClientWithSupportUser } = require('@condo/domains/user/utils/testSchema')
const { makeClientWithServiceUser } = require('@condo/domains/user/utils/testSchema')

const { FLAT_UNIT_TYPE } = require('../../property/constants/common')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { FLAT_UNIT_TYPE } = require('../../property/constants/common')
const { FLAT_UNIT_TYPE } = require('@condo/domains/property/constants/common')

logger.info({ msg: `${SERVICE_NAME} stats`, entityName: 'B2CApp', entityId: b2cAppId, data: { callId, stats }, err: err })
}

async function checkLimits ({ context, b2cAppId, logContext }) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be shared with sendB2CAppPushMessageService (moved into utils)?

Copy link
Copy Markdown
Contributor Author

@YEgorLu YEgorLu Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. In sendB2C it also checks for debug app, which can't be used here... Limit is being checked differently, it's like, no use I guess to move this

})

const searchKey = `${VOIP_INCOMING_CALL_MESSAGE_TYPE}-${b2cAppId}`
const ttl = CACHE_TTL[VOIP_INCOMING_CALL_MESSAGE_TYPE] || CACHE_TTL['DEFAULT']
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the current limitations? 1 call in 2 seconds? What if multiple panels calls at the same time?

Since, its not per user, rate limit it trickier this time

Comment thread apps/condo/domains/miniapp/schema/SendVoIPStartMessageService.js Outdated
Comment on lines +149 to +151
if (voipType === B2C_APP_VOIP_TYPE) {
voipType = NATIVE_VOIP_TYPE
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is going on right here....Why do you insert mess into new code?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just spread custom values

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

above in comment it literally says what is happening.

Somebody configured CustomValue for this contact to use miniapp as call app. But in mutation they did not provide data for miniapp call. In that case we falling back to standard native call. Otherwise we will send empty voip push -> user will see ringing, but after answer nothing would happen

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And custom values are being spread just a few lines below

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants